/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package MetricsRetriever.RetrieverComponents;

import DataStructures.Metrics;
import DataStructures.ProjectInformation;
import General.FileManager;
import General.Globals;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.w3c.dom.Document;
import General.Utilities;
import java.io.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Timer;
import java.util.concurrent.TimeoutException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.tmatesoft.svn.core.SVNException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Manuel
 */

public class CppManager  implements MetricsManager {

    private File f;
    private ArrayList<String> classFiles;
    private HashMap projectMetrics;
    private FileManager fManager;
    private ProjectInformation pInfo;
    private ArrayList<String> toCheck;
    private ArrayList<Integer> wmcs;
    private ArrayList<Integer> dits;
    private ArrayList<Integer> lcoms;
    private ArrayList<Integer> nocs;
    private ArrayList<Integer> fanins;
    private ArrayList<Integer> fanouts;
    private ArrayList<Integer> rfcs;
    private ArrayList<Integer> cbos;
    private Date headDate;

    public CppManager(ProjectInformation pi, FileManager fm){
        fManager = fm;
        pInfo = pi;
        wmcs = new ArrayList<Integer>();
        dits = new ArrayList<Integer>();
        lcoms = new ArrayList<Integer>();
        nocs = new ArrayList<Integer>();
        fanins = new ArrayList<Integer>();
        fanouts = new ArrayList<Integer>();
        rfcs = new ArrayList<Integer>();
        cbos = new ArrayList<Integer>();
        toCheck = new ArrayList<String>();
        projectMetrics = new HashMap();
    }

    public HashMap getAllCodeMetrics(){
        classFiles = fManager.process(Globals.DATA_DIRECTORY+"/"+pInfo.getName()+"/"+Globals.SOURCECODE_DIRECTORY);
        if(classFiles.size()<10)
            return null;
        this.assessMetrics(classFiles);
        //fManager.cleanUp();

        Collection col = projectMetrics.keySet();
        Iterator i = col.iterator();
        while(i.hasNext()){
            String currentKey = (String) i.next();
            Metrics m = (Metrics) projectMetrics.get(currentKey);


        }
        if(projectMetrics == null || projectMetrics.size()<10)
            return null;
        return projectMetrics;
    }

    private void assessMetrics(ArrayList<String> files){
           this.cleanUpList(files);
           System.out.println("Now assessing "+files.size()+ " files for c++");
           Metrics m = new Metrics();
           if(pInfo.getLastRelease() == null)
               return;
           m.setReleaseDate(pInfo.getLastRelease());
           int blocks = (int) Math.ceil(files.size()/100);
           ArrayList<String> commands = new ArrayList<String>();
           for(int j = 0;j<blocks;j++){
               String command = "cccc --xml_outfile="+Globals.DATA_DIRECTORY+"/"+pInfo.getName()+"/metrics"+j+".xml";

               for(int i = (100*j);i<(100*(j+1));i++){
                   if(i<files.size() && !(files.get(i).endsWith(".h")|| files.get(i).endsWith(".hpp")|| files.get(i).endsWith(".hxx")))
                        command += (" "+files.get(i));
               }

               commands.add(command);
           }
           for(int i = 0;i<blocks;i++){
            try {
                CppManager.executeCommandLine(commands.get(i), true, true, 300000);
            } catch (IOException ex) {
                Logger.getLogger(CppManager.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InterruptedException ex) {
                Logger.getLogger(CppManager.class.getName()).log(Level.SEVERE, null, ex);
            } catch (TimeoutException ex) {
                Logger.getLogger(CppManager.class.getName()).log(Level.SEVERE, null, ex);
            }
           }
           String xmlMetrics = "";
           for(int i = 0;i<blocks;i++){
                xmlMetrics = Utilities.readFromFile(Globals.DATA_DIRECTORY+"/"+pInfo.getName()+"/metrics"+i+".xml");
                if(xmlMetrics != null && !xmlMetrics.isEmpty()){
                    this.parseMetrics(xmlMetrics);
                }
           }

           m.setDit(this.computeAverage(dits));
           m.setWmc(this.computeAverage(wmcs));
           m.setLcom(this.computeAverage(lcoms));
           m.setNoc(this.computeAverage(nocs));
           m.setRfc(this.computeAverage(rfcs));
           m.setFanin(this.computeAverage(fanins));
           m.setFanout(this.computeAverage(fanouts));
           m.setCbo(this.computeAverage(cbos));

           m.setMaxdit(this.computeMaximum(dits));
           m.setMaxwmc(this.computeMaximum(wmcs));
           m.setMaxlcom(this.computeMaximum(lcoms));
           m.setMaxnoc(this.computeMaximum(nocs));
           m.setMaxrfc(this.computeMaximum(rfcs));
           m.setMaxfanin(this.computeMaximum(fanins));
           m.setMaxfanout(this.computeMaximum(fanouts));
           m.setMaxcbo(this.computeMaximum(cbos));

           m.setMindit(this.computeMinimum(dits));
           m.setMinwmc(this.computeMinimum(wmcs));
           m.setMinlcom(this.computeMinimum(lcoms));
           m.setMinnoc(this.computeMinimum(nocs));
           m.setMinrfc(this.computeMinimum(rfcs));
           m.setMinfanin(this.computeMinimum(fanins));
           m.setMinfanout(this.computeMinimum(fanouts));
           m.setMincbo(this.computeMinimum(cbos));

           m.setName(".project.");
           projectMetrics.put(m.getName(),m);

    }

    private void parseMetrics(String metrics) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new ByteArrayInputStream(metrics.getBytes()));
            traverse(document.getDocumentElement(), 0);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void traverse(Node n, int level) {

        int i;
        // find out what type of node this is and call the corresponding method

        short t = n.getNodeType();
        if (t == Document.ELEMENT_NODE) {
            if(n.getNodeName().contains("module"))
                this.handleItem(n);

            NodeList nl = n.getChildNodes();
            if (nl.getLength() == 0) {
                return;
            }

            for (i = 0; i < nl.getLength(); i++) {
                traverse(nl.item(i), level + 1);
            }
        }
        if (t == Document.ELEMENT_NODE) {
            //END NODE
        }
    }

    private void handleItem(Node moduleNode){

        Metrics m = new Metrics();
        NodeList nl = moduleNode.getChildNodes();
        Node nameNode = getChild(moduleNode,"name");

        if(nameNode == null)
            return;
        String moduleName = nameNode.getTextContent();

        if(projectMetrics.get(moduleName) != null)
            m = (Metrics) projectMetrics.get(moduleName);

        m.setName(moduleName);
        m.setReleaseDate(headDate);
        for (int i = 0; i < nl.getLength(); i++) {
            Node currentNode = nl.item(i);

            if(currentNode.getNodeName().equals("lines_of_code")){
                String  locString = currentNode.getAttributes().getNamedItem("value").getNodeValue();
                int locs = 0;
                try{
                    locs = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                }catch(Exception e){}
                if(locs>10){
                    toCheck.add(moduleName);
                }
            }

            else if(currentNode.getNodeName().toLowerCase().contains("weighted_methods_per_class_unity") && toCheck.contains(moduleName))
            {
                int wmc = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                wmcs.add(wmc);
                m.setWmc(wmc);
            }

            else if(currentNode.getNodeName().toLowerCase().contains("depth_of_inheritance_tree") && toCheck.contains(moduleName)){
                int dit = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                dits.add(dit);
                m.setDit(dit);
            }

            else if(currentNode.getNodeName().toLowerCase().contains("number_of_children") && toCheck.contains(moduleName)){
                int noc = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                nocs.add(noc);
                m.setNoc(noc);
            }

            else if(currentNode.getNodeName().toLowerCase().contains("coupling_between_objects") && toCheck.contains(moduleName)){
                int cbo = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                cbos.add(cbo);
                m.setCbo(cbo);
            }

            else if(currentNode.getNodeName().toLowerCase().contains("fan_in_visible") && toCheck.contains(moduleName)){
                int fanin = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                fanins.add(fanin);
                m.setFanin(fanin);
            }

            else if(currentNode.getNodeName().toLowerCase().contains("fan_out_visible") && toCheck.contains(moduleName)){
                int rfc = Integer.parseInt(currentNode.getAttributes().getNamedItem("value").getNodeValue());
                rfcs.add(rfc);
                m.setRfc(rfc);
            }
        }
        if(toCheck.contains(moduleName))
            projectMetrics.put(m.getName(),m);
    }

    private void cleanUpList(ArrayList<String> files) {
        for (int i = 0;i<files.size();i++){
            //do clean up all other files
            if(!(files.get(i).endsWith("cpp") || files.get(i).endsWith("c++") || files.get(i).endsWith("cxx") || files.get(i).endsWith("cc") || files.get(i).endsWith("h"))){
               files.remove(i);
            }
        }
    }

    private Node getChild(Node parentNode, String nodeName){
        NodeList map = parentNode.getChildNodes();
        for(int i = 0;i<map.getLength();i++){
            Node current = map.item(i);
            if(current.getNodeName().contains(nodeName))
                return current;
        }
        return null;
    }

    private boolean processIsAlive( Process p ) {
        if(p == null)
            return false;
        try
        {
            p.exitValue();
            return false;
        } catch (IllegalThreadStateException e) {
            return true;
        }
    }

    private void setDate(Date d){
        headDate = d;
    }
    
    

    private double computeAverage(ArrayList<Integer> list){
        if(list.isEmpty())
            return -1d;
        int n = list.size();
        int sum = 0;
        for(int i = 0;i<list.size();i++){
            sum += list.get(i).intValue();
        }
        DecimalFormat twoDForm = new DecimalFormat("#.####");
        if(n==0)
            return 0;
        return Double.valueOf(twoDForm.format((double) sum/n));
    }
    
    private double computeMaximum(ArrayList<Integer> list){

        if(list.isEmpty())
            return -1d;
        int max = -1;

        for(int i = 0;i<list.size();i++){
            if(list.get(i)>max)
                max = list.get(i);
        }

        

        if(max == -1)
            return -1d;
        return ((double) max);

    }
    
    private double computeMinimum(ArrayList<Integer> list){

        if(list.isEmpty())
            return -1d;

        int min = 100000;
        for(int i = 0;i<list.size();i++){
            if(list.get(i)<min)
                min = list.get(i);
        }

        if(min==100000)
            return -1d;
        return ((double) min);

    }


   public static int executeCommandLine(final String commandLine,
                                     final boolean printOutput,
                                     final boolean printError,
                                     final long timeout)
  throws IOException, InterruptedException, TimeoutException
{
  Runtime runtime = Runtime.getRuntime();
  Process process = runtime.exec(commandLine);
  /* Set up process I/O. */

  CommandExecutor worker = new CommandExecutor(process);
  worker.start();
  try {
    worker.join(timeout);
    if (worker.exit != null)
      return worker.exit;
    else
      throw new TimeoutException();
  } catch(InterruptedException ex) {
    worker.interrupt();
    Thread.currentThread().interrupt();
    throw ex;
  } finally {
    process.destroy();
  }
}

private static class CommandExecutor extends Thread {
  private final Process process;
  private Integer exit;
  private CommandExecutor(Process process) {
    this.process = process;
  }
  public void run() {
    try {
      exit = process.waitFor();
    } catch (InterruptedException ignore) {
      return;
    }
  }
}


}
